Skip to content

test#65772

Draft
bobhan1 wants to merge 16 commits into
apache:masterfrom
bobhan1:202607170-test-async-write
Draft

test#65772
bobhan1 wants to merge 16 commits into
apache:masterfrom
bobhan1:202607170-test-async-write

Conversation

@bobhan1

@bobhan1 bobhan1 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: close #xxx

Related PR: #xxx

Problem Summary:

Release note

None

Check List (For Author)

  • Test

    • Regression test
    • Unit Test
    • Manual test (add detailed scripts or steps below)
    • No need to test or manual test. Explain why:
      • This is a refactor/code format and no logic has been changed.
      • Previous test can cover this change.
      • No code files have been changed.
      • Other reason
  • Behavior changed:

    • No.
    • Yes.
  • Does this need documentation?

    • No.
    • Yes.

Check List (For Reviewer who merge this PR)

  • Confirm the release note
  • Confirm test cases
  • Confirm document
  • Add branch pick label

bobhan1 added 14 commits July 15, 2026 18:42
### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary:

On a file-cache miss, CachedRemoteFileReader currently reads the requested data
from remote storage and then appends and finalizes the corresponding local cache
blocks on the query thread. The remote read is required to satisfy the query, but
the subsequent local writes are best-effort cache population. Coupling the two
makes local filesystem latency and backpressure part of foreground scan latency.

Moving only the append call to a background thread is not sufficient. Concurrent
readers may fetch the same missing range, FileBlock downloader ownership has
thread-affine cleanup semantics, and cache clear/remove can invalidate queued
work. Warm-up, prefetch, dry-run, and other explicit cache-population callers also
require synchronous completion semantics.

This change introduces an opt-in asynchronous write path with the following
architecture:

1. BlockFileCache provides a read-only probe API that reports downloaded,
   downloading, empty, and missing ranges without creating cache cells or taking
   downloader ownership.
2. Each cache instance owns an InflightWriteBufferIndex. It publishes remote-read
   buffers with insert-if-absent semantics so later readers can reuse bytes that
   have already been fetched and are awaiting persistence.
3. Each cache disk owns an AsyncCacheWriteService with a bounded MPMC queue,
   tracked-buffer accounting, dynamically resizable workers, task-age protection,
   and an explicit shutdown protocol.
4. The ordinary read path combines inflight buffers and downloaded cache blocks,
   reads the remaining middle range from remote storage once, copies all required
   bytes into the caller buffer, and submits background tasks only for true cache
   misses.
5. Workers revalidate the cache write epoch and current block state before writing.
   Conditional index removal and epoch changes prevent stale callbacks or queued
   tasks from deleting newer entries or recreating data after cache invalidation.

Queue rejection, tracked-buffer pressure, or asynchronous persistence failure does
not fail a remote read that has already produced the requested data. The inflight
entry is rolled back and the operation falls back to best-effort cache behavior.
Explicit cache-population paths continue to use the synchronous implementation.

The feature is disabled by default and can be switched online. Worker counts,
pending-task limits, batch size, and task-age thresholds are validated
through configuration. New bvars and runtime-profile counters expose submissions,
inflight reuse, probe results, rejections, failures, queue depth, buffer memory,
and write latency.

The asynchronous reader implementation is isolated in
cached_remote_file_reader_async_write.cpp. The top-level read function only
orchestrates planning, covered-range materialization, a single remote middle read,
and task submission; the detailed steps are kept in cohesive helper functions.

### Release note

Add an opt-in asynchronous file-cache write path controlled by
`enable_async_file_cache_write`. It is disabled by default.

### Check List (For Author)

- Test:
    - [x] Regression test
        - Docker cloud suite `test_async_file_cache_write`: 1 suite passed
    - [x] Unit Test
        - BE ASAN targeted tests: 26 cases from 4 suites passed
    - [x] Build
        - `./build.sh --be --fe --cloud -j100`
        - `./build.sh --be -j100`
    - [x] Code style
        - `build-support/check-format.sh`
        - clang-tidy was intentionally not run for this change
- Behavior changed:
    - [x] Yes. When explicitly enabled, ordinary file-cache misses return after
      the caller buffer is complete and persist missing cache blocks in background
      workers. The default and explicit cache-population behavior are unchanged.
- Does this need documentation:
    - [x] No. The feature is experimental and disabled by default.
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The phase-one asynchronous file-cache reader built detailed coverage runs, maintained multiple cursors, and materialized individual holes inside a read even though most read_at requests span only one or two cache blocks. This made the query-side orchestration difficult to review and maintain without providing meaningful value for the common case.

Replace that logic with one aligned inflight lookup, one read-only cache probe, and a simple per-block source plan. The reader still gives inflight buffers priority and still distinguishes downloaded, downloading, and missing cache blocks. Downloading blocks outside the remote span retain their wait behavior. When real misses exist, the reader takes the first through last miss as one remote range, intentionally rereads any cache or inflight blocks inside that range, and submits background writes only for the blocks that were actual misses. A cache-side race falls back to one full aligned remote read.

This preserves caller-buffer completeness, inflight deduplication, existing-block reads, cache wait semantics, non-blocking write submission, and backpressure rollback while substantially reducing the amount of control flow in CachedRemoteFileReader::_read_async_write_path and its helpers.

### Release note

None

### Check List (For Author)

- Test:
    - Unit Test: six targeted BlockFileCacheTest cases passed under ASAN, covering inflight reuse, DOWNLOADING wait, cached sides, one remote middle span, real-miss-only submission, backpressure rollback, and per-read mode selection
    - Build: ./build.sh --be -j100 passed
    - Style check: build-support/check-format.sh and git diff --check passed
- Behavior changed: No. This refactor preserves the phase-one asynchronous cache-write behavior while simplifying how the read range is assembled.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The asynchronous cache read planner called BlockFileCache::probe before consulting the inflight write-buffer index. BlockFileCache::probe acquires the cache mutex, so a request already covered entirely by inflight buffers still contended on BlockFileCache even though it needed no cache metadata.

Build the aligned block list and perform the batch inflight lookup first. If every requested block is covered for the current write epoch, return the plan immediately and materialize the caller buffer directly from inflight memory. If any block is not covered, retain the existing mixed-source behavior by issuing one whole-range read-only cache probe and classifying only the non-inflight blocks as downloaded, downloading, or remote misses.

Make the probe result optional in the read plan so ownership matches the conditional probe. Extend the inflight reuse unit test to hold the BlockFileCache mutex during the second read; the read must still complete, directly proving that the full-inflight fast path does not enter BlockFileCache::probe.

### Release note

None

### Check List (For Author)

- Test:
    - Unit Test: six targeted BlockFileCacheTest cases passed under ASAN, including full inflight coverage while the BlockFileCache mutex is held, partial cache coverage, downloading waits, middle-span reads, backpressure rollback, and per-read mode selection
    - Build: ./build.sh --be -j100 passed
    - Style check: build-support/check-format.sh and git diff --check passed
- Behavior changed: Yes. Reads fully covered by current-epoch inflight buffers no longer call BlockFileCache::probe or acquire its cache mutex; partial inflight coverage still probes and combines existing cache blocks.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: AsyncCacheWriteService previously used a follow_global_config flag to switch between fixed test options and direct reads of mutable BE configuration. That made queue admission, batching, and watchdog behavior depend on global state that was not visible in the service interface. It also split online updates across two mechanisms: worker-count changes were forwarded by FileCacheFactory, while the remaining settings were read implicitly from worker and submission paths.

Make configuration ownership explicit. A newly initialized BlockFileCache constructs a complete per-disk options snapshot, and FileCacheFactory registers update callbacks for all five mutable async-write settings. Each callback captures one complete configuration snapshot and forwards it through FileCacheFactory::update_async_write_options to AsyncCacheWriteService::update_options. The service validates the snapshot, applies the requested worker count, and atomically publishes immutable queue, batch, and watchdog settings. Submission and worker paths now consume service-owned snapshots and no longer include or reference common/config.h.

Update unit tests to configure isolated services through the explicit interface, and add coverage proving that config::set_config propagates every mutable setting through the factory into an initialized per-disk service.

### Release note

None

### Check List (For Author)

- Test:
    - Unit Test: `./run-be-ut.sh --run --filter=AsyncCacheWriteServiceTest.*:BlockFileCacheTest.async_write_backpressure_rolls_back_inflight_entry -j 100` passed all 11 tests under ASAN
    - Build: `./build.sh --be -j100` passed
    - Style check: `build-support/check-format.sh` and `git diff --check` passed
- Behavior changed: No. Online mutable settings keep their existing behavior but are propagated through explicit update interfaces.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The phase-one asynchronous file-cache write service used two persistent workers per cache disk. The synchronous path it replaces persisted cache blocks directly on scanner threads, so its effective per-disk write concurrency could scale with the external scanner concurrency, whose default per-context upper bound is 16, and could grow further across concurrent query contexts. A two-worker default therefore serialized writeback far more aggressively than the former path and could fill the bounded pending queue during ordinary scan fan-out.

Increase the default to 16 workers per cache disk. Keep one MPMC queue and let each worker dequeue, revalidate, claim the FileBlock downloader, and write the block in the same thread. Splitting consumption and persistence into separate pools would add a full-task handoff without an independent processing stage, and claiming a downloader before that handoff would violate FileBlock's thread-bound ownership contract. Each worker now uses its own ConsumerToken so concurrent consumers maintain independent producer-stream cursors instead of rescanning streams for every task.

Avoid creating 16 persistent per-disk worker loops while asynchronous writeback is disabled. The cache still constructs the service state and inflight index, but starts workers only when the feature is enabled. A false-to-true online configuration update explicitly starts all initialized services through the factory interface, while mutable service options continue to flow through the explicit factory/service update API. Service readiness is published only after all configured worker loops have been accepted, so query threads reject best-effort submissions instead of enqueueing work to an unready service.

Add deterministic coverage for eight workers consuming distinct tasks concurrently, disabled-service rejection, online enablement, and the existing runtime resize, shutdown, watchdog, inflight cleanup, and reader backpressure rollback behavior.

### Release note

Increase the default asynchronous file-cache write concurrency from 2 to 16 workers per cache disk. Worker threads are created only after asynchronous file-cache writeback is enabled.

### Check List (For Author)

- Test: Unit Test
    - `./build.sh --be -j100`
    - `./run-be-ut.sh --run --filter=AsyncCacheWriteServiceTest.*:BlockFileCacheTest.async_write_backpressure_rolls_back_inflight_entry -j 100` (12 tests passed)
    - `build-support/check-format.sh`
    - `git diff --check`
- Behavior changed: Yes. The default per-disk asynchronous write concurrency is 16, and disabled services no longer keep worker loops resident.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The phase-one async file-cache write implementation had strong happy-path coverage, but several correctness boundaries were not exercised through the complete component interactions. Missing coverage included cache-file disappearance and whole-key self-heal cleanup, wait timeout fallback, direct-read prefix preservation, final concurrent publication deduplication, tracked-buffer allocation failure, external-table cache reuse, worker ownership of existing or deleting cells, remove/write epoch races, runtime worker growth and shrink, and complete propagation of the new profile and synchronous cache-population semantics.

Add compact scenario-oriented BE unit tests that drive the real reader, cache, inflight index, async service, worker, removal, downloader, and index-preload paths. The tests verify both returned data and persistent cache state, including metadata and physical file deletion. Narrow test-only sync points make allocation failures and race windows deterministic without changing normal runtime behavior.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - 51 related ASAN BE unit tests passed with run-be-ut.sh and -j100
    - 7 focused changed-path tests passed with run-be-ut.sh and -j100
    - BE build passed with build.sh --be -j100
    - build-support/check-format.sh passed
- Behavior changed: No; only test coverage and deterministic test injection points are added
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The async cache-write planner queried one block-aligned range, but `BlockFileCache::probe` returned a `FileBlocksHolder` of cache hits plus an independent gap list. The planner then had to scan all hits and gaps for every logical read block even though the read-plan blocks and probe slots use the same block boundaries. This obscured the alignment invariant and introduced unnecessary nested matching logic on the query read path.

Change the probe contract to return one ordered nullable `FileBlock` pointer per aligned input block. A non-null slot is asserted to have the exact corresponding range, except that the final block may end at EOF, while a null slot directly represents a cache miss. The planner now preserves its inflight-first fast path and joins probe results to plan blocks by index; materialization also reads the matching slot directly.

Remove `FileBlocksHolder` and the independent gaps from `FileBlocksProbeResult`. Preserve the existing deferred cleanup semantics for EMPTY and deleting cache blocks through a shared cache-user reference release helper rather than embedding a holder in the probe result. Update focused and end-to-end tests for hit/miss slots, a short final block, retained block states, self-heal cleanup, and an aligned direct-cache prefix followed by an async-written suffix.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - 51 related ASAN BE unit tests passed with `run-be-ut.sh` and `-j100`
    - 2 focused probe/direct-prefix ASAN BE unit tests passed with `run-be-ut.sh` and `-j100`
    - `build-support/check-format.sh` passed
- Behavior changed: No; this simplifies an internal probe/planning contract without changing user-visible cache semantics
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: Existing async file-cache write tests covered a single pending-limit rejection and runtime worker resizing independently, but they did not exercise the complete dynamic backpressure lifecycle. Without that coverage, regressions could allow the MPMC backlog to exceed its bound, lose submissions under producer concurrency, fail to expose sustained rejection at capacity, or leave accepted work stranded after consumers are scaled up.

Add one deterministic service-level BE unit test that stalls the initial worker before cache mutation and drives four producers in controlled waves. The test verifies that the actual queued backlog grows through 4, 8, 12, and 16 tasks, that pending count includes the blocked active task, and that a subsequent 48-task producer burst is rejected without changing the bounded queue or accepted-task count.

After producers stop, the test increases worker concurrency from one to four and batch size from one to four, then releases the artificial write delay. It samples the MPMC backlog independently from pending count, verifies an intermediate lower watermark and an empty queue, and confirms that all accepted tasks finalize with pending count returning to zero. The synchronization point makes both phases deterministic without adding a production-only observation API.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - New dynamic MPMC backpressure ASAN BE unit test passed with run-be-ut.sh and -j100
    - All 14 AsyncCacheWriteServiceTest ASAN BE unit tests passed with run-be-ut.sh and -j100
    - build-support/check-format.sh passed
- Behavior changed: No; this adds deterministic test coverage only
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The async file cache write settings were mixed into the broader block file cache configuration section, while the inflight write buffer index settings did not carry the feature name. This made the feature difficult to locate as one configuration group and made name-based filtering incomplete. Move all async file cache write declarations, definitions, and validators into a dedicated contiguous section. Keep the primary enable_async_file_cache_write switch unchanged, rename only the inflight index enable and shard-count settings with the async_file_cache_write prefix, and update runtime consumers plus BE and regression test configuration. Defaults, mutability, validation, and runtime behavior remain unchanged.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=AsyncCachedRemoteFileReaderTest.*:BlockFileCacheTest.*async_write*:BlockFileCacheTest.cache_write_mode_is_resolved_for_each_read_context:AsyncCacheWriteServiceTest.* -j100 (26 tests passed)
    - build-support/check-format.sh
- Behavior changed: No
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The dynamic async-write backpressure test created four producer threads during queue growth, but each producer submitted only one task per fill wave. That exercised simultaneous entry only weakly and did not model sustained concurrent production before backpressure. Start every producer in a wave through a barrier and let each producer submit four consecutive tasks. The test now observes deterministic queue growth through 16, 32, 48, and 64 queued tasks, verifies a subsequent 128-task concurrent burst is rejected at the pending limit, then confirms the enlarged and accelerated consumer side drains the queue to zero.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=AsyncCachedRemoteFileReaderTest.*:BlockFileCacheTest.*async_write*:BlockFileCacheTest.cache_write_mode_is_resolved_for_each_read_context:AsyncCacheWriteServiceTest.* -j100 (26 tests passed)
    - build-support/check-format.sh
- Behavior changed: No
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: The async file cache write path exposed only an aggregate pending count and a limited set of outcome counters. When throughput degraded, operators could not distinguish producer backpressure, MPMC queue buildup, unavailable workers, inflight-index lock contention, BlockFileCache metadata contention, append/finalize latency, or watchdog and stale-epoch drops.

Add exact atomically maintained queue, active-task, running-worker, configured-capacity, and active-stage gauges. Add reason-specific rejection and watchdog counters, submitted and persisted byte/block throughput, and latency recorders for submission, allocation, queue wait, worker processing, get-or-set, append, finalize, probing, read-plan construction, and write submission. Instrument inflight shard lock wait and hold time, and route probe/get-or-set locking through the existing BlockFileCache lock-wait metric.

Keep queue monitoring passive and exact instead of adding a sampling thread. Remove the unused AsyncCacheWriteService::stats snapshot API and use the service state and metrics directly in tests. Do not expose an inflight index metadata memory estimate because it can be mistaken for total payload memory; async_cache_write_buffer_memory_bytes remains the payload-memory metric.

Extend the BE unit tests to verify live queue and stage gauges, metric counters and latency samples, lock instrumentation, and the concurrent MPMC growth, rejection, scale-up, and drain flow.

### Release note

Add monitoring metrics for async file cache write queue pressure, worker activity, stage latency, throughput, rejection causes, and lock contention.

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh -j100 --run --filter=AsyncCacheWriteServiceTest.*:InflightWriteBufferIndexTest.*:BlockFileCacheTest.Probe*:AsyncCachedRemoteFileReaderTest.* (29 tests passed under ASAN_UT)
- Behavior changed: Yes. Adds observability only; async cache read and write semantics are unchanged.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The existing file-cache microbenchmark either includes object-store and network latency through CachedRemoteFileReader or stops at BlockFileCache::get_or_set. It cannot isolate phase-1 asynchronous writeback, distinguish caller return time from background drain, or expose queue saturation and inflight-index contention.

Add a standalone Release microbenchmark beside the existing tool. It combines a deterministic in-memory remote reader with a real filesystem-backed BlockFileCache and covers three layers: synchronous versus asynchronous cold-miss reader latency with complete-range persistence verification; producer admission, bounded MPMC queue behavior, worker scaling, backpressure, and real get_or_set/append/finalize persistence; and sharded miss/hit versus single-hot-key InflightWriteBufferIndex contention.

Each case emits machine-readable latency percentiles, throughput, accepted/rejected/persisted counts, and pending/queued/inflight high-water marks. Benchmark data defaults to output/ so it remains untracked and uses the larger workspace disk.

### Release note

None

### Check List (For Author)

- Test: Manual test
    - ./build.sh --be --file-cache-microbench -j100
    - Existing file_cache_get_or_set benchmark with 1 and 32 threads
    - Full async benchmark in all mode with 16 producers and worker counts 1,4,16
    - build-support/check-format.sh
    - git diff --check
- Behavior changed: No (benchmark tooling only)
- Does this need documentation: No (tool README updated)
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: The asynchronous file-cache write microbenchmark previously emitted only one sample per case and did not establish the storage baseline of the cache filesystem. These short concurrent cases are sensitive to scheduler activity, page-cache state, filesystem metadata, and background writeback, so a single number can hide material variance and make worker-scaling conclusions unreliable.

Run every selected reader, service, and inflight-index case five times by default and add the one-based repetition to each machine-readable RESULT line. Add an installed runner that can measure direct 1 MiB sequential QD1 and random QD16 writes on the same filesystem before starting the benchmark.

The fio behavior is explicit and does not make fio a mandatory dependency:

| RUN_FIO | fio available | Behavior |
| --- | --- | --- |
| auto (default) | Yes | Run both disk baselines, then run the cache benchmark |
| auto (default) | No | Print DISK_BASELINE skipped and continue directly with the cache benchmark |
| 1 | No | Fail because the caller explicitly required fio |
| 0 | Any | Skip fio and run the cache benchmark |

The runner uses a unique sibling directory under the selected cache path, unlinks fio data, and keeps direct I/O out of the page cache. The benchmark rejects non-empty cache paths instead of recursively clearing them, and suppresses INFO logging so merged stdout and stderr cannot corrupt RESULT records.

Expand the tool README with the component flow, coverage and non-goals of each group, default workload, field semantics, fio controls, cache-path ownership, repetition methodology, and interpretation guidance. Median is the primary value and the observed minimum and maximum are retained.

Release experiment configuration: /dev/nvme11n1 ext4, 1 MiB blocks, 64 KiB caller reads, 16 producers, 128 reader operations, 256 service attempts, and five repetitions.

fio direct-I/O baseline:

| Workload | Bandwidth | p95 completion latency |
| --- | ---: | ---: |
| 1 MiB sequential write, QD1 | 2513 MiB/s | 161 us |
| 1 MiB random write, QD16 | 3106 MiB/s | 10.552 ms |

CachedRemoteFileReader foreground results:

| Write mode | Median ops/s | Minimum ops/s | Maximum ops/s | Median average latency |
| --- | ---: | ---: | ---: | ---: |
| Synchronous | 5645 | 5124 | 7649 | 1459 us |
| Asynchronous | 6739 | 4739 | 8298 | 912 us |

The asynchronous median was 19.4% higher in throughput and 37.5% lower in average latency. The overlapping ranges are retained because they show why a single run is insufficient.

AsyncCacheWriteService verified completion results:

| Workers | Median MiB/s | Minimum MiB/s | Maximum MiB/s | Median drain time |
| ---: | ---: | ---: | ---: | ---: |
| 1 | 798 | 730 | 968 | 0.300 s |
| 4 | 1562 | 1260 | 1751 | 0.153 s |
| 16 | 7825 | 5255 | 13203 | 0.014 s |

These values measure buffered append and finalize completion without fsync. They are not durable-media throughput and are not directly comparable with the direct-I/O fio baseline.

Bounded backpressure results:

| Metric | Median | Minimum | Maximum |
| --- | ---: | ---: | ---: |
| Accepted tasks | 76 | 64 | 101 |
| Rejected tasks | 180 | 155 | 192 |
| Peak pending | 64 | 64 | 64 |
| Peak queued | 48 | 48 | 48 |
| Peak inflight | 65 | 65 | 67 |

Every accepted task was verified as persisted, and peak pending stayed at the configured limit.

InflightWriteBufferIndex lookup results:

| Workload | Median ops/s | Minimum ops/s | Maximum ops/s | Median average latency |
| --- | ---: | ---: | ---: | ---: |
| Sharded miss | 5.531M | 4.028M | 6.621M | 2.688 us |
| Sharded hit | 4.198M | 3.270M | 6.036M | 3.171 us |
| Hot-key hit | 1.104M | 1.090M | 1.268M | 13.701 us |

All 45 RESULT records were complete and parseable. All reader ranges and all accepted service tasks passed final BlockFileCache coverage verification.

### Release note

None

### Check List (For Author)

- Test: Manual test
    - ./build.sh --be --file-cache-microbench -j100 (Release)
    - ./output/be/bin/run-async-file-cache-write-microbench.sh --benchmark_mode=all --cache_path=./output/async_file_cache_write_microbench_repeat_5_clean --producer_threads=16 --reader_workers=16 --worker_counts=1,4,16 --repetitions=5
    - Non-empty cache-path rejection with sentinel preservation
    - build-support/clang-format.sh
    - build-support/check-format.sh
    - bash -n and shellcheck for the runner
    - git diff --check
- Behavior changed: No (benchmark tooling only)
- Does this need documentation: No (tool README updated)
@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@bobhan1

bobhan1 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 29861 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit 184797aece3c16a94d53ad34137b7fdfe4ac4425, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17791	4124	4124	4124
q2	2000	326	214	214
q3	10282	1523	877	877
q4	4684	476	346	346
q5	7539	894	598	598
q6	182	169	134	134
q7	768	814	616	616
q8	9359	1569	1653	1569
q9	5570	4409	4354	4354
q10	6752	1728	1501	1501
q11	502	365	346	346
q12	744	607	466	466
q13	18060	3483	2803	2803
q14	262	265	246	246
q15	q16	789	776	710	710
q17	1005	957	937	937
q18	6999	5749	5634	5634
q19	1195	1282	1033	1033
q20	798	667	608	608
q21	5743	2607	2441	2441
q22	438	353	304	304
Total cold run time: 101462 ms
Total hot run time: 29861 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4444	4381	4412	4381
q2	302	320	219	219
q3	4561	4971	4416	4416
q4	2060	2156	1349	1349
q5	4395	4301	4239	4239
q6	226	173	128	128
q7	1743	2035	1831	1831
q8	2579	2191	2238	2191
q9	7982	8232	7738	7738
q10	4690	4743	4209	4209
q11	558	426	387	387
q12	748	776	550	550
q13	3322	3590	2870	2870
q14	307	320	283	283
q15	q16	727	733	650	650
q17	1365	1376	1471	1376
q18	7788	7527	7389	7389
q19	1213	1073	1081	1073
q20	2214	2210	1932	1932
q21	5232	4594	4457	4457
q22	524	485	406	406
Total cold run time: 56980 ms
Total hot run time: 52074 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 178044 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit 184797aece3c16a94d53ad34137b7fdfe4ac4425, data reload: false

query5	4322	632	509	509
query6	484	245	209	209
query7	4831	633	366	366
query8	339	195	174	174
query9	8794	4067	4127	4067
query10	484	365	296	296
query11	5905	2350	2116	2116
query12	157	101	99	99
query13	1334	620	450	450
query14	6220	5224	4882	4882
query14_1	4256	4252	4264	4252
query15	215	201	178	178
query16	995	459	458	458
query17	899	699	545	545
query18	2421	478	335	335
query19	202	189	146	146
query20	106	107	104	104
query21	230	162	137	137
query22	13490	13647	13270	13270
query23	17453	16517	16087	16087
query23_1	16204	16283	16146	16146
query24	7509	1760	1279	1279
query24_1	1293	1271	1260	1260
query25	549	430	351	351
query26	1325	351	205	205
query27	2627	587	358	358
query28	4520	2003	2011	2003
query29	1073	617	467	467
query30	346	266	231	231
query31	1118	1095	991	991
query32	114	66	62	62
query33	540	334	269	269
query34	1175	1182	648	648
query35	770	773	670	670
query36	1192	1174	1034	1034
query37	153	106	94	94
query38	1882	1708	1669	1669
query39	882	864	849	849
query39_1	842	832	849	832
query40	272	171	144	144
query41	70	68	71	68
query42	98	98	95	95
query43	321	324	277	277
query44	1493	792	786	786
query45	201	183	176	176
query46	1065	1185	728	728
query47	2100	2107	1999	1999
query48	409	404	318	318
query49	605	425	318	318
query50	1126	452	333	333
query51	10793	10808	10812	10808
query52	89	91	78	78
query53	265	284	211	211
query54	296	247	239	239
query55	77	74	73	73
query56	306	337	306	306
query57	1297	1294	1201	1201
query58	292	279	274	274
query59	1571	1642	1407	1407
query60	317	295	271	271
query61	176	202	144	144
query62	543	492	427	427
query63	243	217	203	203
query64	2834	1026	849	849
query65	4711	4629	4662	4629
query66	1841	508	411	411
query67	29213	29199	28405	28405
query68	3273	1564	978	978
query69	434	314	266	266
query70	1061	966	944	944
query71	405	336	331	331
query72	3102	2797	2332	2332
query73	821	772	430	430
query74	5145	4926	4701	4701
query75	2543	2498	2145	2145
query76	2373	1233	811	811
query77	365	369	283	283
query78	11846	11775	11249	11249
query79	1319	1186	748	748
query80	672	544	462	462
query81	458	335	297	297
query82	575	158	119	119
query83	394	329	296	296
query84	324	160	131	131
query85	915	617	520	520
query86	346	274	263	263
query87	1817	1879	1749	1749
query88	3786	2799	2807	2799
query89	443	375	331	331
query90	1963	199	198	198
query91	206	188	164	164
query92	63	62	54	54
query93	1525	1565	1023	1023
query94	552	351	324	324
query95	788	497	452	452
query96	1057	799	358	358
query97	2613	2610	2512	2512
query98	221	210	201	201
query99	1078	1107	962	962
Total cold run time: 262583 ms
Total hot run time: 178044 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 24.93 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit 184797aece3c16a94d53ad34137b7fdfe4ac4425, data reload: false

query1	0.00	0.00	0.00
query2	0.09	0.04	0.04
query3	0.25	0.14	0.13
query4	1.62	0.14	0.14
query5	0.23	0.23	0.23
query6	1.28	1.12	1.06
query7	0.04	0.01	0.01
query8	0.06	0.04	0.04
query9	0.38	0.30	0.30
query10	0.55	0.57	0.55
query11	0.18	0.14	0.14
query12	0.18	0.14	0.14
query13	0.47	0.49	0.47
query14	1.00	1.00	1.00
query15	0.61	0.59	0.59
query16	0.32	0.32	0.30
query17	1.07	1.10	1.09
query18	0.22	0.20	0.21
query19	2.03	1.94	1.99
query20	0.02	0.01	0.01
query21	15.43	0.20	0.13
query22	4.93	0.06	0.05
query23	16.16	0.30	0.14
query24	2.95	0.43	0.34
query25	0.12	0.05	0.04
query26	0.74	0.20	0.14
query27	0.05	0.04	0.03
query28	3.53	0.95	0.52
query29	12.49	4.16	3.31
query30	0.27	0.16	0.17
query31	2.76	0.58	0.31
query32	3.24	0.60	0.48
query33	3.14	3.30	3.12
query34	15.67	4.24	3.52
query35	3.47	3.48	3.55
query36	0.56	0.43	0.42
query37	0.09	0.06	0.06
query38	0.05	0.04	0.03
query39	0.03	0.03	0.03
query40	0.18	0.17	0.15
query41	0.08	0.04	0.03
query42	0.04	0.03	0.03
query43	0.04	0.03	0.04
Total cold run time: 96.62 s
Total hot run time: 24.93 s

bobhan1 added 2 commits July 20, 2026 11:37
### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: File writers allocate cache cells at the normal full block size before the final upload buffer size is known. The final partial block is shrunk to its actual byte count only during FileBlock::finalize(). An async cache read racing with that interval builds a logical tail block ending at file EOF, but BlockFileCache::probe required the cached right boundary to match it exactly. The mismatch aborted the BE in probe; the async read plan and materialization path also carried the same exact-range assumption.

Allow the final short probe slot to be covered by a larger preallocated cache block while preserving exact-size assertions for complete slots. Keep the async reader stricter by allowing the larger boundary only for the logical block that ends at the real file EOF. Add a focused probe unit test that reproduced the original fatal assertion and an async CachedRemoteFileReader test that exercises the complete EOF read flow. The reproducer aborted before the fix and both tests pass after it.

### Release note

Fix a BE crash when an async file-cache read races with preallocation of a partial final file block.

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=BlockFileCacheTest.ProbeAcceptsPreallocatedBlockCoveringFileTail:AsyncCachedRemoteFileReaderTest.preallocated_cache_block_can_cover_the_short_file_tail -j100 (ASAN, 2 tests passed)
- Behavior changed: Yes. Async cache reads now accept a full-size preallocated cache block that covers the short EOF block.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65658

Problem Summary: A systematic review after the file-tail crash found that FileBlocksProbeResult reused FileBlocksHolder cleanup semantics. Destroying a read-only probe result on the same thread as an independently owned downloader therefore called complete_unlocked(), reset a valid DOWNLOADING block to EMPTY, and cleared its downloader. A focused BEUT reproduced that state transition before the fix.

Give holder and probe references explicit cleanup roles: holders still complete downloader ownership acquired through get_or_set(), while probes only retain blocks and perform the existing deferred EMPTY/deleting-cell cleanup. Also stop re-reading the mutable FileBlock range after probe() has validated slot coverage under the cache mutex; a concurrent file writer may shrink a preallocated EOF block during finalize(), so the async reader now consistently uses its immutable logical plan range for cache offsets and diagnostics.

The review added concise end-to-end coverage for a DOWNLOADING preallocated tail that finalizes while a reader waits, mixed existing-cache and inflight coverage, and operation with the optional inflight index disabled. The fixture now resets the process-wide FD cache together with FileCacheFactory because its key omits the per-test cache path; without that isolation, newly added cases exposed stale descriptors from earlier cases.

### Release note

Fix async file-cache read races involving read-only probe lifetime and concurrent finalization of a preallocated file-tail block.

### Check List (For Author)

- Test: Unit Test
    - Pre-fix reproduction: BlockFileCacheTest.ProbeResultDoesNotCompleteDownloaderOwnedByCaller failed because the block became EMPTY and its downloader was cleared
    - Targeted ASAN BEUT: 6 focused probe/EOF/cache-inflight/external-table cases passed with -j100
    - Relevant ASAN BEUT sweep: 58 of 60 passed and exposed two cross-case FDCache isolation failures; after the isolation fix, the complete affected AsyncCachedRemoteFileReaderTest suite passed 9 of 9 with -j100, while the other 51 relevant tests had already passed in the sweep
    - build-support/clang-format.sh, build-support/check-format.sh, and git diff --check passed
- Behavior changed: Yes. Read-only probes no longer complete downloader ownership, and async reads remain valid while a preallocated EOF block is finalized and shrunk.
- Does this need documentation: No
@bobhan1

bobhan1 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 29891 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit bce089a63b40c44705fd24416bb83544fffb2dab, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17614	4234	4165	4165
q2	2000	327	206	206
q3	10348	1466	848	848
q4	4724	473	340	340
q5	7819	858	587	587
q6	259	172	142	142
q7	812	808	602	602
q8	9885	1639	1634	1634
q9	5679	4360	4372	4360
q10	6773	1799	1463	1463
q11	509	358	327	327
q12	740	572	468	468
q13	18096	3330	2771	2771
q14	269	266	246	246
q15	q16	784	776	707	707
q17	965	1030	1024	1024
q18	6958	5737	5602	5602
q19	1280	1211	1026	1026
q20	785	687	570	570
q21	5863	2569	2507	2507
q22	431	355	296	296
Total cold run time: 102593 ms
Total hot run time: 29891 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4544	4412	4397	4397
q2	288	337	216	216
q3	4585	4964	4411	4411
q4	2108	2186	1486	1486
q5	4453	4335	4324	4324
q6	237	178	125	125
q7	2122	2018	1612	1612
q8	2492	2131	2132	2131
q9	7915	7695	7877	7695
q10	4624	4654	4223	4223
q11	594	439	527	439
q12	737	754	552	552
q13	3386	3580	3068	3068
q14	300	309	278	278
q15	q16	706	730	643	643
q17	1361	1404	1411	1404
q18	8223	7506	6967	6967
q19	1102	1094	1072	1072
q20	2225	2196	1942	1942
q21	5320	4632	4480	4480
q22	515	476	401	401
Total cold run time: 57837 ms
Total hot run time: 51866 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 177701 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit bce089a63b40c44705fd24416bb83544fffb2dab, data reload: false

query5	4322	625	496	496
query6	465	220	221	220
query7	4860	596	364	364
query8	333	187	173	173
query9	8784	4135	4138	4135
query10	480	366	327	327
query11	5891	2325	2162	2162
query12	155	102	105	102
query13	1269	604	430	430
query14	6284	5214	4887	4887
query14_1	4264	4225	4244	4225
query15	241	196	173	173
query16	1028	469	433	433
query17	1096	702	561	561
query18	2515	455	339	339
query19	199	185	143	143
query20	109	108	105	105
query21	228	159	133	133
query22	13550	13569	13289	13289
query23	17176	16537	16172	16172
query23_1	16255	16209	16206	16206
query24	7496	1763	1253	1253
query24_1	1292	1287	1279	1279
query25	541	451	382	382
query26	1318	362	209	209
query27	2575	618	364	364
query28	4474	1986	1988	1986
query29	1067	612	470	470
query30	336	267	225	225
query31	1123	1098	973	973
query32	108	62	62	62
query33	549	330	266	266
query34	1146	1167	604	604
query35	770	779	676	676
query36	1224	1200	1053	1053
query37	162	119	101	101
query38	1878	1711	1660	1660
query39	884	875	848	848
query39_1	827	864	841	841
query40	248	167	150	150
query41	72	69	69	69
query42	97	93	103	93
query43	333	336	283	283
query44	1423	778	771	771
query45	195	185	179	179
query46	1056	1234	690	690
query47	2110	2086	2032	2032
query48	414	416	302	302
query49	582	435	340	340
query50	1149	449	348	348
query51	10844	10891	10630	10630
query52	95	89	77	77
query53	279	311	212	212
query54	299	252	274	252
query55	77	75	70	70
query56	309	331	300	300
query57	1299	1296	1179	1179
query58	299	269	291	269
query59	1635	1668	1448	1448
query60	331	292	269	269
query61	178	173	179	173
query62	547	504	440	440
query63	249	209	208	208
query64	2959	1099	840	840
query65	4729	4629	4688	4629
query66	1832	501	384	384
query67	29436	29322	29040	29040
query68	3229	1640	1063	1063
query69	408	303	277	277
query70	1045	935	938	935
query71	367	342	313	313
query72	3026	2766	2396	2396
query73	855	831	428	428
query74	5054	4902	4697	4697
query75	2518	2502	2135	2135
query76	2331	1221	826	826
query77	354	382	269	269
query78	11970	11954	11390	11390
query79	1581	1199	800	800
query80	1261	568	464	464
query81	551	332	296	296
query82	609	159	120	120
query83	372	331	304	304
query84	286	161	133	133
query85	957	599	510	510
query86	423	286	277	277
query87	1825	1810	1753	1753
query88	3731	2821	2777	2777
query89	440	377	335	335
query90	1880	192	191	191
query91	198	186	166	166
query92	61	63	57	57
query93	1730	1518	963	963
query94	705	353	303	303
query95	750	489	547	489
query96	1105	823	353	353
query97	2649	2616	2507	2507
query98	213	205	207	205
query99	1081	1119	956	956
Total cold run time: 264333 ms
Total hot run time: 177701 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 25.11 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit bce089a63b40c44705fd24416bb83544fffb2dab, data reload: false

query1	0.00	0.00	0.01
query2	0.10	0.05	0.05
query3	0.26	0.14	0.14
query4	1.60	0.14	0.14
query5	0.23	0.24	0.22
query6	1.22	1.09	1.09
query7	0.04	0.01	0.00
query8	0.05	0.04	0.04
query9	0.37	0.31	0.32
query10	0.54	0.55	0.61
query11	0.19	0.14	0.13
query12	0.18	0.14	0.14
query13	0.46	0.46	0.47
query14	1.03	1.02	1.00
query15	0.61	0.60	0.58
query16	0.33	0.34	0.34
query17	1.12	1.10	1.10
query18	0.23	0.22	0.21
query19	2.00	2.01	1.96
query20	0.01	0.01	0.01
query21	15.42	0.20	0.13
query22	4.92	0.06	0.05
query23	16.14	0.32	0.12
query24	2.91	0.43	0.32
query25	0.11	0.05	0.04
query26	0.71	0.21	0.15
query27	0.04	0.04	0.04
query28	3.55	0.88	0.54
query29	12.50	4.09	3.26
query30	0.27	0.16	0.16
query31	2.76	0.60	0.31
query32	3.22	0.59	0.49
query33	3.20	3.22	3.29
query34	15.72	4.24	3.51
query35	3.54	3.51	3.53
query36	0.56	0.42	0.43
query37	0.09	0.06	0.06
query38	0.06	0.04	0.04
query39	0.04	0.02	0.02
query40	0.18	0.18	0.16
query41	0.09	0.03	0.03
query42	0.04	0.03	0.03
query43	0.04	0.03	0.04
Total cold run time: 96.68 s
Total hot run time: 25.11 s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants